/**
 * DEVELOPED BY
 * GIL LOPES BUENO
 * gilbueno.mail@gmail.com
 *
 * WORKS WITH:
 * IE8*, IE 9+, FF 4+, SF 5+, WebKit, CH 7+, OP 12+, BESEN, Rhino 1.7+
 * For IE8 (and other legacy browsers) WatchJS will use dirty checking  
 *
 * FORK:
 * https://github.com/melanke/Watch.JS
 *
 * LICENSE: MIT
 */

"use strict";
(function (factory) {
	if (typeof exports === 'object') {
		// 在严格的CommonJS环境下不工作,必须支持module.exports
		module.exports = factory();
	} else if (typeof define === 'function' && define.amd) {
		// AMD. Register as an anonymous module.
		// AMD注册成匿名模块
		define(factory);
	} else {
		// 浏览器环境
		window.WatchJS = factory();
		// 参数: 监视对象，[监视属性，回调函数，监听深度，]
		window.watch = window.WatchJS.watch;
		window.unwatch = window.WatchJS.unwatch;
		window.callWatchers = window.WatchJS.callWatchers;
	}
}(function () {

	var WatchJS = {
			// 使用WatchJS.suspend(obj)代替
			noMore: false, // use WatchJS.suspend(obj) instead
			// 仅使用脏检查去追踪更改
			useDirtyCheck: false, // use only dirty checking to track changes.
			preserveExistingSetters: false
		},
		// 
		lengthsubjects = [];
	// 脏检查列表
	var dirtyChecklist = [];
	// 使用来自defineProperty 和 __defineSetter__的更改 将应用的更改列表
	var pendingChanges = []; // used coalesce changes from defineProperty and __defineSetter__
	// 标记是否支持Object.defineProperty方法
	var supportDefineProperty = false; // 检查是否支持defineProperty
	try {
		supportDefineProperty = Object.defineProperty && Object.defineProperty({}, 'x', {});
	} catch (ex) {
		/* not supported */ }

	var isFunction = function (functionToCheck) {
		var getType = {};
		// 检查是否为函数类型
		// 将Object原型上的toString方法切换到检测函数的环境中调用,
		// 而不是调用函数类型重写后的toString方法
		return functionToCheck && getType.toString.call(functionToCheck) == '[object Function]';
	};

	var isInt = function (x) {
		return x % 1 === 0;
	};

	var isArray = function (obj) {
		return Object.prototype.toString.call(obj) === '[object Array]';
	};

	var isObject = function (obj) {
		return {}.toString.apply(obj) === '[object Object]';
	};
	// 字面上理解是获取对象的差别
	var getObjDiff = function (a, b) {
		var aplus = [], // aplus数组存储属于a但不属于b的属性
			bplus = []; // 反之bplus数组存储属于b但不属于a的属性

		if (!(typeof a == "string") && !(typeof b == "string")) {
			// 若两对象都不是字符串
			if (isArray(a) && b) {
				// a为数组
				for (var i = 0; i < a.length; i++) {
					if (b[i] === undefined) aplus.push(i);
				}
			} else {
				// 遍历a中的属性,将属于a但不在b中的属性放到aplus数组中
				for (var i in a) {
					if (a.hasOwnProperty(i)) {
						if (b && !b.hasOwnProperty(i)) {
							aplus.push(i);
						}
					}
				}
			}

			if (isArray(b) && a) {
				for (var j = 0; j < b.length; j++) {
					if (a[j] === undefined) bplus.push(j);
				}
			} else {
				for (var j in b) {
					if (b.hasOwnProperty(j)) {
						if (a && !a.hasOwnProperty(j)) {
							bplus.push(j);
						}
					}
				}
			}
		}

		return {
			added: aplus, //aplus中为增加的属性,属于a但不属于b
			removed: bplus //bplus中为移除的属性,属于b但不属于a
		}
	};
	// 深拷贝
	var clone = function (obj) {

		if (null == obj || "object" != typeof obj) {
			// 若对象为空或不为object类型则直接返回该变量
			return obj;
		}
		//获取对象的构造函数返回,与var a = new b()类似
		var copy = obj.constructor();

		for (var attr in obj) {
			// 将对象的属性拷贝到构造函数里
			copy[attr] = obj[attr];
		}

		return copy;

	}

	// 定义访问器属性
	var getExistingSetter = function (obj, propName) {
		if (WatchJS.preserveExistingSetters) {
			var existing = Object.getOwnPropertyDescriptor(obj, propName);
			return existing.set;
		}

		return undefined;
	}

	var defineGetAndSet = function (obj, propName, getter, setter) {
		try {
			var existingSetter = getExistingSetter(obj, propName);
			Object.defineProperty(obj, propName, {
				get: getter,
				set: function (value) {
					setter.call(this, value, true); // coalesce changes
					if (existingSetter) {
						existingSetter(value);
					}
				},
				enumerable: true,
				configurable: true
			});
		} catch (e1) {
			try {
				Object.prototype.__defineGetter__.call(obj, propName, getter);
				Object.prototype.__defineSetter__.call(obj, propName, function (value) {
					setter.call(this, value, true); // coalesce changes
				});
			} catch (e2) {
				observeDirtyChanges(obj, propName, setter);
				//throw new Error("watchJS error: browser not supported :/")
			}
		}

	};
	// 定义属性
	var defineProp = function (obj, propName, value) {
		try {
			Object.defineProperty(obj, propName, {
				enumerable: false, // 不可迭代
				configurable: true, // 可配置
				writable: false, // 不可修改
				value: value
			});
		} catch (error) {
			obj[propName] = value;
		}
	};
	// 观察脏数据更改
	var observeDirtyChanges = function (obj, propName, setter) {
		// 将对象的属性添加到dirtyChecklist数组中(脏数据检查列表)
		dirtyChecklist[dirtyChecklist.length] = {
			prop: propName,
			object: obj,
			orig: clone(obj[propName]), // 将指定属性拷贝到orig中
			callback: setter
		}
	}
	// 主API 
	// 参数：变量，属性，回调函数
	var watch = function () {
		if (isFunction(arguments[1])) {
			// 监听一个对象的所有属性的变化
			watchAll.apply(this, arguments);
		} else if (isArray(arguments[1])) {
			// 监听一个对象的多个属性,属性名称用数组传递
			watchMany.apply(this, arguments);
		} else {
			// 监听一个对象的一个属性
			watchOne.apply(this, arguments);
		}

	};
	// 监听一个对象的所有属性
	var watchAll = function (obj, watcher, level, addNRemove) {
		// 仅接收对象和数组
		if ((typeof obj == "string") || (!(obj instanceof Object) && !isArray(obj))) {
			//accepts only objects and array (not string)
			return;
		}

		if (isArray(obj)) {
			// 数组
			// watch all changes on the array
			defineWatcher(obj, "__watchall__", watcher, level);
			if (level === undefined || level > 0) {
				for (var prop = 0; prop < obj.length; prop++) { // watch objects in array
					watchAll(obj[prop], watcher, level, addNRemove);
				}
			}
		} else {
			// 对象
			var prop, props = [];
			for (prop in obj) { //for each attribute if obj is an object
				if (prop == "$val" || (!supportDefineProperty && prop === 'watchers')) {
					continue;
				}

				if (Object.prototype.hasOwnProperty.call(obj, prop)) {
					props.push(prop); //put in the props
				}
			}
			watchMany(obj, props, watcher, level, addNRemove); //watch all items of the props
		}

		if (addNRemove) {
			pushToLengthSubjects(obj, "$$watchlengthsubjectroot", watcher, level);
		}
	};
	// 监听一个对象的多个属性
	var watchMany = function (obj, props, watcher, level, addNRemove) {

		if ((typeof obj == "string") || (!(obj instanceof Object) && !isArray(obj))) { //accepts only objects and array (not string)
			return;
		}

		for (var i = 0; i < props.length; i++) { //watch each property
			var prop = props[i];
			watchOne(obj, prop, watcher, level, addNRemove);
		}

	};
	// 监听一个对象的一个属性
	var watchOne = function (obj, prop, watcher, level, addNRemove) {
		// 若变量为字符串或者其不是对象和数组则返回,不能监听该变量
		if ((typeof obj == "string") || (!(obj instanceof Object) && !isArray(obj))) { //accepts only objects and array (not string)
			return;
		}
		// 若指定变量的属性为函数也不能watch
		if (isFunction(obj[prop])) { //dont watch if it is a function
			return;
		}
		// 若对应属性不为空且level未定义或大于0,则进行递归处理
		if (obj[prop] != null && (level === undefined || level > 0)) {
			//recursively watch all attributes of this
			// 递归监听每一个属性
			watchAll(obj[prop], watcher, level !== undefined ? level - 1 : level);
		}

		defineWatcher(obj, prop, watcher, level);

		if (addNRemove && (level === undefined || level > 0)) {
			pushToLengthSubjects(obj, prop, watcher, level);
		}

	};
	// 取消监听 主API
	var unwatch = function () {

		if (isFunction(arguments[1])) {
			unwatchAll.apply(this, arguments);
		} else if (isArray(arguments[1])) {
			unwatchMany.apply(this, arguments);
		} else {
			unwatchOne.apply(this, arguments);
		}

	};

	var unwatchAll = function (obj, watcher) {

		if (obj instanceof String || (!(obj instanceof Object) && !isArray(obj))) { //accepts only objects and array (not string)
			return;
		}

		if (isArray(obj)) {
			var props = ['__watchall__'];
			for (var prop = 0; prop < obj.length; prop++) { //for each item if obj is an array
				props.push(prop); //put in the props
			}
			unwatchMany(obj, props, watcher); //watch all itens of the props
		} else {
			var unwatchPropsInObject = function (obj2) {
				var props = [];
				for (var prop2 in obj2) { //for each attribute if obj is an object
					if (obj2.hasOwnProperty(prop2)) {
						if (obj2[prop2] instanceof Object) {
							unwatchPropsInObject(obj2[prop2]); //recurs into object props
						} else {
							props.push(prop2); //put in the props
						}
					}
				}
				unwatchMany(obj2, props, watcher); //unwatch all of the props
			};
			unwatchPropsInObject(obj);
		}
	};

	var unwatchMany = function (obj, props, watcher) {

		for (var prop2 in props) { //watch each attribute of "props" if is an object
			if (props.hasOwnProperty(prop2)) {
				unwatchOne(obj, props[prop2], watcher);
			}
		}
	};

	var timeouts = [],
		timerID = null;

	function clearTimerID() {
		timerID = null;
		for (var i = 0; i < timeouts.length; i++) {
			timeouts[i]();
		}
		timeouts.length = 0;
	}
	var getTimerID = function () {
		if (!timerID) {
			timerID = setTimeout(clearTimerID);
		}
		return timerID;
	}
	var registerTimeout = function (fn) { // register function to be called on timeout
		if (timerID == null) getTimerID();
		timeouts[timeouts.length] = fn;
	}

	// Track changes made to an array, object or an object's property 
	// and invoke callback with a single change object containing type, value, oldvalue and array splices
	// Syntax: 
	//      trackChange(obj, callback, recursive, addNRemove)
	//      trackChange(obj, prop, callback, recursive, addNRemove)
	// 主API
	var trackChange = function () {
		var fn = (isFunction(arguments[2])) ? trackProperty : trackObject;
		fn.apply(this, arguments);
	}

	// track changes made to an object and invoke callback with a single change object containing type, value and array splices
	var trackObject = function (obj, callback, recursive, addNRemove) {
		var change = null,
			lastTimerID = -1;
		var isArr = isArray(obj);
		var level, fn = function (prop, action, newValue, oldValue) {
			var timerID = getTimerID();
			if (lastTimerID !== timerID) { // check if timer has changed since last update
				lastTimerID = timerID;
				change = {
					type: 'update'
				}
				change['value'] = obj;
				change['splices'] = null;
				registerTimeout(function () {
					callback.call(this, change);
					change = null;
				});
			}
			// create splices for array changes
			if (isArr && obj === this && change !== null) {
				if (action === 'pop' || action === 'shift') {
					newValue = [];
					oldValue = [oldValue];
				} else if (action === 'push' || action === 'unshift') {
					newValue = [newValue];
					oldValue = [];
				} else if (action !== 'splice') {
					return; // return here - for reverse and sort operations we don't need to return splices. a simple update will do
				}
				if (!change.splices) change.splices = [];
				change.splices[change.splices.length] = {
					index: prop,
					deleteCount: oldValue ? oldValue.length : 0,
					addedCount: newValue ? newValue.length : 0,
					added: newValue,
					deleted: oldValue
				};
			}

		}
		level = (recursive == true) ? undefined : 0;
		watchAll(obj, fn, level, addNRemove);
	}

	// track changes made to the property of an object and invoke callback with a single change object containing type, value, oldvalue and splices
	var trackProperty = function (obj, prop, callback, recursive, addNRemove) {
		if (obj && prop) {
			watchOne(obj, prop, function (prop, action, newvalue, oldvalue) {
				var change = {
					type: 'update'
				}
				change['value'] = newvalue;
				change['oldvalue'] = oldvalue;
				if (recursive && isObject(newvalue) || isArray(newvalue)) {
					trackObject(newvalue, callback, recursive, addNRemove);
				}
				callback.call(this, change);
			}, 0)

			if (recursive && isObject(obj[prop]) || isArray(obj[prop])) {
				trackObject(obj[prop], callback, recursive, addNRemove);
			}
		}
	}
	// 定义一个监听器（对象，属性名，回调函数，优先级）
	var defineWatcher = function (obj, prop, watcher, level) {
		var newWatcher = false;
		var isArr = isArray(obj);

		if (!obj.watchers) {
			// 若变量还没有设置watcher,则定义一个watchers对象属性
			defineProp(obj, "watchers", {});
			if (isArr) {
				// watch array functions
				// 监听数组 函数
				watchFunctions(obj, function (index, action, newValue, oldValue) {
					// 
					addPendingChange(obj, index, action, newValue, oldValue);
					if (level !== 0 && newValue && (isObject(newValue) || isArray(newValue))) {
						var i, n, ln, wAll, watchList = obj.watchers[prop];
						if ((wAll = obj.watchers['__watchall__'])) {
							watchList = watchList ? watchList.concat(wAll) : wAll;
						}
						ln = watchList ? watchList.length : 0;
						for (i = 0; i < ln; i++) {
							if (action !== 'splice') {
								watchAll(newValue, watchList[i], (level === undefined) ? level : level - 1);
							} else {
								// watch spliced values
								for (n = 0; n < newValue.length; n++) {
									watchAll(newValue[n], watchList[i], (level === undefined) ? level : level - 1);
								}
							}
						}
					}
				});
			}
		}
		// 若相应属性的监听器为空，则设置为空数组
		if (!obj.watchers[prop]) {
			obj.watchers[prop] = [];
			if (!isArr) newWatcher = true; // 设置监听标志为true
		}
		// 遍历相应属性的监听器数组，若重复定义则返回
		for (var i = 0; i < obj.watchers[prop].length; i++) {
			if (obj.watchers[prop][i] === watcher) {
				return;
			}
		}
		// 添加一个新的对应属性的watcher到watcher数组中
		obj.watchers[prop].push(watcher); //add the new watcher to the watchers array
		// 若已设置回调函数，则设置相应属性的setter和getter
		if (newWatcher) {
			var val = obj[prop];
			// 直接返回属性值
			var getter = function () {
				return val;
			};
			// 闭包 记录之前的old value
			var setter = function (newval, delayWatcher) {
				var oldval = val;
				val = newval;
				if (level !== 0 &&
					obj[prop] && (isObject(obj[prop]) || isArray(obj[prop])) &&
					!obj[prop].watchers) {
					// watch sub properties
					// 监听子属性，及当前属性值为对象或数组
					var i, ln = obj.watchers[prop].length;
					for (i = 0; i < ln; i++) {
						watchAll(obj[prop], obj.watchers[prop][i], (level === undefined) ? level : level - 1);
					}
				}
				//watchFunctions(obj, prop);
				if (isSuspended(obj, prop)) {
					resume(obj, prop);
					return;
				}

				if (!WatchJS.noMore) { // this does not work with Object.observe
					//if (JSON.stringify(oldval) !== JSON.stringify(newval)) {
					if (oldval !== newval) {
						if (!delayWatcher) {
							callWatchers(obj, prop, "set", newval, oldval);
						} else {
							addPendingChange(obj, prop, "set", newval, oldval);
						}
						WatchJS.noMore = false;
					}
				}
			};

			if (WatchJS.useDirtyCheck) {
				observeDirtyChanges(obj, prop, setter);
			} else {
				defineGetAndSet(obj, prop, getter, setter);
			}
		}

	};
	// 调用监视器
	var callWatchers = function (obj, prop, action, newval, oldval) {
		if (prop !== undefined) {
			var ln, wl, watchList = obj.watchers[prop];
			if ((wl = obj.watchers['__watchall__'])) {
				watchList = watchList ? watchList.concat(wl) : wl;
			}
			ln = watchList ? watchList.length : 0;
			for (var wr = 0; wr < ln; wr++) {
				watchList[wr].call(obj, prop, action, newval, oldval);
			}
		} else {
			for (var prop in obj) { //call all
				if (obj.hasOwnProperty(prop)) {
					callWatchers(obj, prop, action, newval, oldval);
				}
			}
		}
	};

	var methodNames = ['pop', 'push', 'reverse', 'shift', 'sort', 'slice', 'unshift', 'splice'];
	// 定义数组的监听方法
	var defineArrayMethodWatcher = function (obj, original, methodName, callback) {
		defineProp(obj, methodName, function () {
			var index = 0;
			var i, newValue, oldValue, response;
			// get values before splicing array 
			if (methodName === 'splice') {
				var start = arguments[0];
				var end = start + arguments[1];
				oldValue = obj.slice(start, end);
				newValue = [];
				// splice语法为splice(1,3,'newvalue1','newvalue2')
				// arguments[2]为第一个新增的元素
				for (i = 2; i < arguments.length; i++) {
					newValue[i - 2] = arguments[i];
				}
				index = start;
			} else {
				newValue = arguments.length > 0 ? arguments[0] : undefined;
			}

			response = original.apply(obj, arguments);
			if (methodName !== 'slice') {
				if (methodName === 'pop') {
					oldValue = response;
					index = obj.length;
				} else if (methodName === 'push') {
					index = obj.length - 1;
				} else if (methodName === 'shift') {
					oldValue = response;
				} else if (methodName !== 'unshift' && newValue === undefined) {
					newValue = response;
				}
				callback.call(obj, index, methodName, newValue, oldValue)
			}
			return response;
		});
	};

	var watchFunctions = function (obj, callback) {

		if (!isFunction(callback) || !obj || (obj instanceof String) || (!isArray(obj))) {
			return;
		}

		for (var i = methodNames.length, methodName; i--;) {
			// 为该属性绑定改造后的数组方法用于监听属性变化
			methodName = methodNames[i];
			defineArrayMethodWatcher(obj, obj[methodName], methodName, callback);
		}

	};

	var unwatchOne = function (obj, prop, watcher) {
		if (prop) {
			if (obj.watchers && obj.watchers[prop]) {
				if (watcher === undefined) {
					delete obj.watchers[prop]; // remove all property watchers
				} else {
					for (var i = 0; i < obj.watchers[prop].length; i++) {
						var w = obj.watchers[prop][i];
						if (w == watcher) {
							obj.watchers[prop].splice(i, 1);
						}
					}
				}
			}
		} else {
			delete obj.watchers;
		}

		removeFromLengthSubjects(obj, prop, watcher);
		removeFromDirtyChecklist(obj, prop);
	};

	// suspend watchers until next update cycle
	// 主API 延迟监听到下一次更新循环
	var suspend = function (obj, prop) {
		if (obj.watchers) {
			var name = '__wjs_suspend__' + (prop !== undefined ? prop : '');
			obj.watchers[name] = true;
		}
	}
	// 检查是否是延时属性
	var isSuspended = function (obj, prop) {
		return obj.watchers &&
			(obj.watchers['__wjs_suspend__'] ||
				obj.watchers['__wjs_suspend__' + prop]);
	}

	// resumes preivously suspended watchers
	// 继续之前延迟的监听器
	var resume = function (obj, prop) {
		registerTimeout(function () {
			delete obj.watchers['__wjs_suspend__'];
			delete obj.watchers['__wjs_suspend__' + prop];
		})
	}

	var pendingTimerID = null;
	// 添加将要发生的变化
	var addPendingChange = function (obj, prop, mode, newval, oldval) {
		pendingChanges[pendingChanges.length] = {
			obj: obj,
			prop: prop,
			mode: mode,
			newval: newval,
			oldval: oldval
		};
		if (pendingTimerID === null) {
			pendingTimerID = setTimeout(applyPendingChanges);
		}
	};
	// 应用改变
	var applyPendingChanges = function () {
		// apply pending changes
		var change = null;
		pendingTimerID = null;
		for (var i = 0; i < pendingChanges.length; i++) {
			change = pendingChanges[i];
			callWatchers(change.obj, change.prop, change.mode, change.newval, change.oldval);
		}
		if (change) {
			pendingChanges = [];
			change = null;
		}
	}

	var loop = function () {
		// check for new or deleted props
		// 检查新的或者删除的属性
		for (var i = 0; i < lengthsubjects.length; i++) {

			var subj = lengthsubjects[i];

			if (subj.prop === "$$watchlengthsubjectroot") {

				var difference = getObjDiff(subj.obj, subj.actual);

				if (difference.added.length || difference.removed.length) {
					if (difference.added.length) {
						watchMany(subj.obj, difference.added, subj.watcher, subj.level - 1, true);
					}

					subj.watcher.call(subj.obj, "root", "differentattr", difference, subj.actual);
				}
				subj.actual = clone(subj.obj);


			} else {

				var difference = getObjDiff(subj.obj[subj.prop], subj.actual);

				if (difference.added.length || difference.removed.length) {
					if (difference.added.length) {
						for (var j = 0; j < subj.obj.watchers[subj.prop].length; j++) {
							watchMany(subj.obj[subj.prop], difference.added, subj.obj.watchers[subj.prop][j], subj.level - 1, true);
						}
					}

					callWatchers(subj.obj, subj.prop, "differentattr", difference, subj.actual);
				}

				subj.actual = clone(subj.obj[subj.prop]);

			}

		}

		// start dirty check
		var n, value;
		if (dirtyChecklist.length > 0) {
			for (var i = 0; i < dirtyChecklist.length; i++) {
				n = dirtyChecklist[i];
				value = n.object[n.prop];
				if (!compareValues(n.orig, value)) {
					n.orig = clone(value);
					n.callback(value);
				}
			}
		}

	};

	var compareValues = function (a, b) {
		var i, state = true;
		if (a !== b) {
			if (isObject(a)) {
				for (i in a) {
					if (!supportDefineProperty && i === 'watchers') continue;
					if (a[i] !== b[i]) {
						state = false;
						break;
					};
				}
			} else {
				state = false;
			}
		}
		return state;
	}

	var pushToLengthSubjects = function (obj, prop, watcher, level) {

		var actual;

		if (prop === "$$watchlengthsubjectroot") {
			actual = clone(obj);
		} else {
			actual = clone(obj[prop]);
		}

		lengthsubjects.push({
			obj: obj,
			prop: prop,
			actual: actual,
			watcher: watcher,
			level: level
		});
	};

	var removeFromLengthSubjects = function (obj, prop, watcher) {
		for (var i = 0; i < lengthsubjects.length; i++) {
			var subj = lengthsubjects[i];

			if (subj.obj == obj) {
				if (!prop || subj.prop == prop) {
					if (!watcher || subj.watcher == watcher) {
						// if we splice off one item at position i
						// we need to decrement i as the array is one item shorter
						// so when we increment i in the loop statement we
						// will land at the correct index.
						// if it's not decremented, you won't delete all length subjects
						lengthsubjects.splice(i--, 1);
					}
				}
			}
		}

	};

	var removeFromDirtyChecklist = function (obj, prop) {
		var notInUse;
		for (var i = 0; i < dirtyChecklist.length; i++) {
			var n = dirtyChecklist[i];
			var watchers = n.object.watchers;
			notInUse = (
				n.object == obj &&
				(!prop || n.prop == prop) &&
				watchers &&
				(!prop || !watchers[prop] || watchers[prop].length == 0)
			);
			if (notInUse) {
				// we use the same syntax as in removeFromLengthSubjects
				dirtyChecklist.splice(i--, 1);
			}
		}

	};

	setInterval(loop, 50);

	WatchJS.watch = watch;
	WatchJS.unwatch = unwatch;
	WatchJS.callWatchers = callWatchers;
	// 延迟监听
	WatchJS.suspend = suspend; // suspend watchers
	// 跟踪对对象或属性的更改，并返回一个简单的更改对象
	WatchJS.onChange = trackChange; // track changes made to object or  it's property and return a single change object

	return WatchJS;

}));
